using System;
using System.Text;
using System.IO;
using System.Threading;
using GeoFramework;
using GeoFramework.Gps;
using GeoFramework.Gps.Nmea;

namespace GeoFramework.Gps.Emulators
{

    /// <summary>
    /// Represents a base class for building a NMEA-0183 GPS device emulator.
    /// </summary>
    /// <remarks>This powerful class behaves like a real GPS device,
    /// except that data is simulated instead of received.  This class can be used as the backing store for an
    /// NmeaStream or NmeaInterpreter class.  to build an emulator, override the DoSentences()
    /// method and use it to generate NMEA data.</remarks>
    public abstract class NmeaEmulator : Emulator //, IDisposable
    {
        #region Private Variables

        private const int MaximumInterpolationIterations = 50;

        private DifferentialGps pDifferentialGps = new DifferentialGps();
        private bool pIsFixed;
        private FixMethod pFixMethod = FixMethod.NoFix;
        private FixMode pFixMode = FixMode.Manual;
        private FixQuality pFixQuality = FixQuality.NoFix;
        private DilutionOfPrecision pHorizontalDilutionOfPrecision = DilutionOfPrecision.Maximum;
        private Longitude pMagneticVariation = Longitude.Empty;
        private DilutionOfPrecision pMeanDilutionOfPrecision = DilutionOfPrecision.Maximum;
        private SatelliteCollection pSatellites;
        private DilutionOfPrecision pVerticalDilutionOfPrecision = DilutionOfPrecision.Maximum;
		private Random NewRandom = new Random();


        #endregion

        #region Constructors

		/// <summary>Creates a new instance.</summary>
        protected NmeaEmulator()
            : this("GPS.NET Device Emulator [NMEA-0183] (http://www.geoframeworks.com)")
        {
        }

		/// <summary>Creates a new instance using the specified thread name.</summary>
		/// <param name="threadName">A <strong>String</strong> describing the friendly name of the emulator.</param>
        protected NmeaEmulator(string threadName)
            : base(threadName)
        {
            if (pSatellites == null)
                pSatellites = SatelliteCollection.Random();
        }

        #endregion

		/// <summary>Performs the generation of emulated data to send to an interpreter.</summary>
        protected override void OnEmulation()
        {
			SatelliteCollection NewerSatellites = new SatelliteCollection();
            // Modify satellite signals

            int FixCount = 0;
            lock (pSatellites.SyncRoot)
            {
                foreach (Satellite satellite in pSatellites)
                {
                    if (NewRandom.Next(6) == 2)
                    {
                        // Change the signal slightly for realism
                        if (NewRandom.Next(2) == 0)
                        {
                            int NewValue = satellite.SignalToNoiseRatio.Increment().Value;
                            if (NewValue > 50)
                                NewValue = 50;
                            NewerSatellites.Add(new Satellite(satellite.PseudorandomNumber,
                                satellite.Azimuth, satellite.Elevation,
                                new SignalToNoiseRatio(NewValue)));
                        }
                        else
                        {
                            int NewValue = satellite.SignalToNoiseRatio.Decrement().Value;
                            if (NewValue < 0)
                                NewValue = 0;
                            NewerSatellites.Add(new Satellite(satellite.PseudorandomNumber,
                                          satellite.Azimuth, satellite.Elevation,
                                          new SignalToNoiseRatio(NewValue)));
                        }
                    }
                    // Get the number of fixed satellites
                    if (satellite.IsFixed)
                        FixCount++;
                }

                // Blend changes of the satellites with the updated collection
                pSatellites.MergeWith(NewerSatellites);
            }
            Thread.Sleep(0);

            // Now adjust DOP values based on the number of fixed satellites
            switch (FixCount)
            {
                case 0:
                    pHorizontalDilutionOfPrecision = DilutionOfPrecision.Maximum;
                    pVerticalDilutionOfPrecision = DilutionOfPrecision.Maximum;
                    pMeanDilutionOfPrecision = DilutionOfPrecision.Maximum;
                    break;
                case 1:
                case 2:
                    // Then set max DOP
                    pHorizontalDilutionOfPrecision = DilutionOfPrecision.Maximum;
                    pVerticalDilutionOfPrecision = DilutionOfPrecision.Maximum;
                    pMeanDilutionOfPrecision = DilutionOfPrecision.Maximum;
                    break;
                case 3:
                    // Fair DOP, only 3 fixed satellites
                    pHorizontalDilutionOfPrecision = DilutionOfPrecision.Fair;
                    pVerticalDilutionOfPrecision = DilutionOfPrecision.Fair;
                    pMeanDilutionOfPrecision = DilutionOfPrecision.Fair;
                    break;
                case 4:
                    // Good DOP
                    pHorizontalDilutionOfPrecision = DilutionOfPrecision.Good;
                    pVerticalDilutionOfPrecision = DilutionOfPrecision.Good;
                    pMeanDilutionOfPrecision = DilutionOfPrecision.Good;
                    break;
                case 5:
                    // Excellent DOP
                    pHorizontalDilutionOfPrecision = DilutionOfPrecision.Excellent;
                    pVerticalDilutionOfPrecision = DilutionOfPrecision.Excellent;
                    pMeanDilutionOfPrecision = DilutionOfPrecision.Excellent;
                    break;
                default:
                    // Several fixed satellites
                    pHorizontalDilutionOfPrecision = DilutionOfPrecision.Ideal;
                    pVerticalDilutionOfPrecision = DilutionOfPrecision.Ideal;
                    pMeanDilutionOfPrecision = DilutionOfPrecision.Ideal;
                    break;
            }
        }


		public override string Protocol
		{
			get
			{
				return "NMEA-0183";
			}
		}


        public override string ToString()
        {
            return "NMEA-0183 GPS Device Emulator";
        }

        #region Public Properties



		/// <summary>Controls simulated differential GPS fix information.</summary>
        public DifferentialGps DifferentialGps
        {
            get
            {
                return pDifferentialGps;
            }
            set
            {
                pDifferentialGps = value;
            }
        }

		/// <summary>Controls the simulated fix method.</summary>
        public FixMethod FixMethod
        {
            get
            {
                return pFixMethod;
            }
            set
            {
                pFixMethod = value;
            }
        }

		/// <summary>Controls the simulated fix mode.</summary>
        public FixMode FixMode
        {
            get
            {
                return pFixMode;
            }
            set
            {
                pFixMode = value;
            }
        }

		/// <summary>Controls the simulated fix quality.</summary>
        public FixQuality FixQuality
        {
            get
            {
                return pFixQuality;
            }
            set
            {
                pFixQuality = value;
            }
        }

		/// <summary>Controls the simulated positional accuracy.</summary>
        public DilutionOfPrecision HorizontalDilutionOfPrecision
        {
            get
            {
                return pHorizontalDilutionOfPrecision;
            }
            set
            {
                pHorizontalDilutionOfPrecision = value;
            }
        }

		/// <summary>Controls whether or not a simulated fix is active.</summary>
        public bool IsFixed
        {
            get
            {
                return pIsFixed;
            }
            set
            {
                pIsFixed = value;
            }
        }

		/// <summary>Controls the simulated magnetic variation.</summary>
        public Longitude MagneticVariation
        {
            get
            {
                return pMagneticVariation;
            }
            set
            {
                pMagneticVariation = value;
            }
        }

		/// <summary>Controls the simulated overall precision.</summary>
        public DilutionOfPrecision MeanDilutionOfPrecision
        {
            get
            {
                return pMeanDilutionOfPrecision;
            }
            set
            {
                pMeanDilutionOfPrecision = value;
            }
        }



		/// <summary>Controls the collection of simulated GPS satellites.</summary>
        public SatelliteCollection Satellites
        {
            get
            {
                return pSatellites;
            }
            set
            {
                pSatellites = value;
            }
        }



		/// <summary>Controls the simulated amount of vertical precision.</summary>
        public DilutionOfPrecision VerticalDilutionOfPrecision
        {
            get
            {
                return pVerticalDilutionOfPrecision;
            }
            set
            {
                pVerticalDilutionOfPrecision = value;
            }
        }


		/// <summary>Queues simulated data to be read by an interpreter.</summary>
		/// <param name="sentence">
		/// An <strong>NmeaSentence</strong> to be placed on the stream for an interpreter to
		/// read.
		/// </param>
        protected void WriteToClient(NmeaSentence sentence)
        {
            WriteToClient(sentence.Value);
        }
        #endregion

    }
}

